﻿using myDocumentClustering.DocumentTermMatrix;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace myDocumentClustering.SAM
{
    /// <summary>
    /// HACCluster is a cluster holding some doc-term vectors.
    /// And provide necessary properties of the cluster. Such as poles, central point, etc.
    /// </summary>
    public class HACCluster
    {
        public List<DTV> vectors = new List<DTV>();

        public HACCluster Left, Right;

        private DTV left_pole, right_pole;
        
        /// <summary>
        /// return the sub-cluster number contributed by this cluster after splitting
        /// Polrization is very delicate. We need to avoid skewness.
        /// </summary>
        /// <returns></returns>
        public List<HACCluster> Polarize()
        {
            List<HACCluster> polarizeResult = new List<HACCluster>();

            if(vectors.Count<=1)
            {
                polarizeResult.Add(this);
            }
            else
            {
                Left = new HACCluster();
                Right = new HACCluster();

                //refine: do we MUST split here? more criteria?
                if (vectors.Count == 2)
                {
                    left_pole = vectors[0];
                    right_pole = vectors[1];
                    Left.AddVector(left_pole);
                    Right.AddVector(right_pole);
                }
                else
                {
                    Get2Poles();

                    //we need finer logic here
                    foreach (var v in vectors)
                    {
                        float cosineSimilarityToLeft = DTV.CalculateVectorsSimilarity(v, left_pole);
                        float cosineSimilarityToRight = DTV.CalculateVectorsSimilarity(v, right_pole);
                        if (cosineSimilarityToLeft > cosineSimilarityToRight)
                        {
                            Left.AddVector(v);
                        }
                        else
                        {
                            Right.AddVector(v);
                        }
                    }
                }
                polarizeResult.Add(Left);
                polarizeResult.Add(Right);
            }

            if (this.vectors.Count > 1 && this.vectors.Count > Left.vectors.Count + Right.vectors.Count)
            {
                Console.WriteLine("Something is missing!");
            }


            return polarizeResult;

        }

        private void Get2Poles()
        {
            //refine: we may have other options here.
            DTV v1 = PickRandomVector();
            DTV v2 = FindOppositeVector(v1);
            if(v2==null)
            {
                Console.WriteLine("v2 is null");
            }
            DTV v3 = FindOppositeVector(v2);
            //it is possible that v2 == v3 that will lead to empty cluster
            if (v3 == null)
            {
                Console.WriteLine("v3 is null");
            }
            if(v2.docIndex==v3.docIndex)
            {
                Console.WriteLine("v2 == v3");
            }

            left_pole = v2;
            right_pole = v3;
        }

        private DTV PickRandomVector()
        {
            Random rand = new Random(DateTime.Now.Second);
            Int32 randomIndex = rand.Next(vectors.Count);
            return vectors[randomIndex];
        }

        private DTV FindOppositeVector(DTV v)
        {
            float vectorDistance = float.MaxValue;
            DTV opposite = null;
            foreach (var vector in vectors)
            {
                if (vector.docIndex != v.docIndex)
                {
                    float dist = DTV.CalculateVectorsSimilarity(v, vector);
                    if (dist < vectorDistance)
                    {
                        opposite = vector;
                        vectorDistance = dist;
                    }
                }
            }
            return opposite;
        }

        public void AddVector(DTV v)
        {
            vectors.Add(v);
        }

        /// <summary>
        /// What if a cluster is empty?
        /// </summary>
        /// <param name="c1"></param>
        /// <param name="c2"></param>
        /// <returns></returns>
        public static float GetClustersSimilarity(HACCluster c1, HACCluster c2)
        {
            DTV v1 = c1.GetClusterVector();
            DTV v2 = c2.GetClusterVector();
            return DTV.CalculateVectorsSimilarity(v1, v2);
        }
        
        /// <summary>
        /// options: sum of all DTVs, average of all DTVs.
        /// Refine: need more thinking here!
        /// </summary>
        /// <returns></returns>
        private DTV GetClusterVector()
        {
            //return GetClusterVectorBySumAndNormalize();
            return GetClusterMedoids();
        }

        private DTV GetClusterVectorBySumAndNormalize()
        {
            DTV clusterVector = new DTV();
            for (Int32 i = 0; i < vectors.Count; i++)
            {
                clusterVector += vectors[i];
            }
            clusterVector.NormalizeIt();

            return clusterVector;
        }

        private DTV GetClusterMedoids()
        {
            DTV clusterCenter = new DTV();
            for (Int32 i = 0; i < vectors.Count; i++)
            {
                clusterCenter += vectors[i];
            }

            clusterCenter /= vectors.Count; // Medoids: geo center
            clusterCenter.NormalizeIt();

            return clusterCenter;
        }


        /// <summary>
        /// Cluster +
        /// </summary>
        /// <param name="clusteri"></param>
        /// <param name="clusterj"></param>
        /// <returns></returns>
        public static HACCluster operator +(HACCluster clusteri, HACCluster clusterj)
        {
            //cluster i <-- cluster i + j
            clusteri.vectors.AddRange(clusterj.vectors);
            return clusteri;
        }

    }
}
